package com.db.bankapp.domain;

import java.util.concurrent.atomic.AtomicReference;

/**
 * @ThreadSafe
 * NonBlocking queue implementation
 */
class NonBlockingQueue<T> implements Queue<T> {
    private static final long BACK_OFF_TIME = 10;
    private static final Node EMPTY_NODE = new Node(null);
    private final AtomicReference<Node<T>> head = new AtomicReference<>(EMPTY_NODE);
    private final AtomicReference<Node<T>> tail = new AtomicReference<>(head.get());

    @Override
    public void add(T elem) throws InterruptedException {
        if (elem == null) {
            throw new IllegalArgumentException("Element must not be null!");
        }
        while (true) {
            Node<T> newTail = new Node<>(elem);
            Node<T> t = tail.get();
            Node<T> n = t.next();
            if (n == null) {
                if (t.casNext(null, newTail)) {
                    if (!head.compareAndSet(EMPTY_NODE, newTail)) {
                        System.err.println("Y???!!!");
                    }
                    if (!tail.compareAndSet(t, newTail)) {
                        System.err.println("FFFFUUUUUU");
                    }
                    return;
                }
            }
            await();
        }
    }

    @Override
    public T remove() throws InterruptedException {
        while (true) {
            Node<T> h = head.get();
            T value = h.value();
            if (value != null && h.casValue(value, null)) {
                Node<T> n;
                if (h.next() == null) {
                    n = EMPTY_NODE;
                    if (!tail.compareAndSet(h, n)) {
                        System.err.println("OOOOOps!");
                    }
                } else {
                    n = h.next();
                }
                if (!head.compareAndSet(h, n)) {
                    System.err.println("IMPOSIIIBBBROOO!");
                }
                return value;
            } else if (h.next() == null) {
                // empty queue
                return null;
            }
            await();
        }
    }

    private void await() throws InterruptedException {
        try {
            Thread.sleep(BACK_OFF_TIME);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw e;
        }
    }

    @Override
    public boolean isEmpty() {
        return head.get() == tail.get();
    }

    private static class Node<T> {
        private final AtomicReference<Node<T>> next = new AtomicReference<>();
        private final AtomicReference<T> value = new AtomicReference<>();

        private Node(T value) {
            this.value.set(value);
        }

        public boolean casNext(Node<T> exp, Node<T> upd) {
            return this.next.compareAndSet(exp, upd);
        }

        public Node<T> next() {
            return next.get();
        }

        public T value() {
            return value.get();
        }

        public boolean casValue(T exp, T upd) {
            return value.compareAndSet(exp, upd);
        }

        private Node() {}
    }
}
